22.2 操作

和调度器对P.runq队列的处理方式类似。每个poolLocal有两个缓存区域:其中区域private完全私有,无须任何锁操作,优先级最高;另一区域share,允许被其他poolLocal访问,用来平衡调度缓存对象,需要加锁处理。不过调度并非时刻发生,这个锁多数时候仅面对当前线程,所以对性能影响并不大。

pool.go

func (p *Pool) Get() interface{} { // 返回 poolLocal l := p.pin() // 优先从 private 选择 x := l.private l.private = nil if x != nil { return x } // 加锁,从 share 区域获取 l.Lock() // 从 shared 尾部提取缓存对象 last := len(l.shared) - 1 if last >= 0 { x = l.shared[last] l.shared = l.shared[:last] } l.Unlock() if x != nil { return x } // 如果提取失败,则需要获取新的缓存对象 return p.getSlow() }

如果从本地获取缓存失败,则考虑从其他poolLocal借调(就是惯偷)一个过来。如果实在不行,则调用New函数新建(最终手段)。

pool.go

func (p *Pool) getSlow() (x interface{}) { size := atomic.LoadUintptr(&p.localSize) local := p.local // 当前 P.id pid := runtime_procPin() // 从其他 poolLocal 偷取一个缓存对象 for i := 0; i < int(size); i++ { // 获取目标 poolLocal,且保证不是自身 l := indexLocal(local, (pid+i+1)%int(size)) // 对目标 poolLocal 加锁,以便访问其 share 区域 l.Lock() // 偷取一个缓存对象 last := len(l.shared) - 1 if last >= 0 { x = l.shared[last] l.shared = l.shared[:last] l.Unlock() break } l.Unlock() } // 偷取失败,使用 New 函数新建 if x == nil && p.New != nil { x = p.New() } return x }

注意:Get操作后,缓存对象彻底与Pool失去引用关联,需要自行Put放回。

至于Put操作,就更简单了,无须考虑不同poolLocal之间的平衡调度。

pool.go

func (p *Pool) Put(x interface{}) { if x == nil { return } // 获取 poolLocal l := p.pin() // 优先放入 private if l.private == nil { l.private = x x = nil } if x == nil { return } // 放入 share l.Lock() l.shared = append(l.shared, x) l.Unlock() }